package com.zzs.common.security.interceptor;

import com.zzs.common.core.constant.JwtConsts;
import com.zzs.common.core.constant.PathConsts;
import com.zzs.common.core.enums.ResponseCode;
import com.zzs.common.core.enums.UserRole;
import com.zzs.common.core.util.ThreadLocalUtil;
import com.zzs.common.security.exception.ServiceException;
import com.zzs.common.security.service.TokenService;
import jakarta.annotation.Nonnull;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * Token 拦截器，拦截请求并进行身份认证和权限校验。
 * <p>
 * 主要功能：
 * <ul>
 *     <li>拦截请求，解析 JWT 并获取用户 ID</li>
 *     <li>根据请求路径校验用户权限</li>
 *     <li>在 Token 即将过期时，自动刷新 Token 并返回给客户端</li>
 * </ul>
 * </p>
 *
 * @author zzs
 * @date 2025/03/17
 */
@Slf4j
@Component
@ComponentScan({"com.zzs.common.security.service"})
public class TokenInterceptor implements HandlerInterceptor {
    @Autowired
    private TokenService tokenService;

    /**
     * 在请求到达控制器之前执行，进行身份验证和权限校验
     *
     * @param request  HTTP 请求
     * @param response HTTP 响应
     * @param handler  处理器
     * @return 是否继续执行后续流程
     */
    @Override
    public boolean preHandle(HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull Object handler) {
        log.info("请求路径: {}", request.getRequestURI());
        log.info("请求方法: {}", request.getMethod());
        String authHeader = request.getHeader(JwtConsts.AUTHORIZATION);
//        如果无法获取userId, 说明token过期, 在tokenService中抛出异常
        long userId = tokenService.getUserId(authHeader);
        ThreadLocalUtil.setUserId(userId);
//        从redis中获取userRole, 如果无法获取, 说明token过期, 在tokenService中抛出异常
        int userRole = tokenService.getUserRole(userId);

        verifyPermissions(request, userRole);
        refreshToken(request, response, authHeader, userId);
        return true;
    }

    /**
     * 校验token是否合法，是否有效，是否有权限访问相应路径。
     *
     * @param request HTTP 请求
     */
    private void verifyPermissions(HttpServletRequest request, int userRole) {
        String requestPath = request.getRequestURI();
        if (requestPath.startsWith(PathConsts.CLIENT_REQUEST_PREFIX)) {
//            客户端请求校验权限
            if (userRole != UserRole.ORDINARY.getRole()) {
                throw new ServiceException(ResponseCode.FORBIDDEN);
            }
        } else if (requestPath.startsWith(PathConsts.SERVER_REQUEST_PREFIX)) {
//            服务端请求校验权限
            if (userRole != UserRole.ADMIN.getRole()) {
                throw new ServiceException(ResponseCode.FORBIDDEN);
            }
        }
    }

    /**
     * 检查jwt是否需要刷新。如果需要刷新，则重制redis键的有效期，并生成新的token给响应头
     *
     * @param response   HTTP 响应
     * @param authHeader token
     * @param userId     用户id
     */
    private void refreshToken(HttpServletRequest request, HttpServletResponse response, String authHeader, long userId) {
        if (request.getRequestURI().endsWith("/logout")) {
            response.setHeader(JwtConsts.AUTHORIZATION, "");
            return;
        }
        String token = "";
//        剩余有效时间
        long remainingTime = tokenService.getRemainingTime(authHeader);
//         若 Token 即将过期，则刷新redis有效期并生成新的 Token 并返回给客户端
        if (remainingTime <= JwtConsts.TOKEN_REFRESH_TIME) {
            token = JwtConsts.BEARER_PREFIX + tokenService.refreshCacheAndCreateNewToken(userId);
        }
        response.setHeader(JwtConsts.AUTHORIZATION, token);
    }


    /**
     * 在控制器方法执行后执行，清空ThreadLocal
     *
     * @param request      HTTP 请求
     * @param response     HTTP 响应
     * @param handler      处理器
     * @param modelAndView 视图模型
     */
    @Override
    public void postHandle(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull Object handler, ModelAndView modelAndView) {
//        清除线程变量，避免线程复用时数据污染
        ThreadLocalUtil.removeUserId();
    }
}
